package com.spatio.serving.basic.service.template.server.web;

import com.spatio.serving.basic.service.template.mysql.Result.Result;
import com.spatio.serving.common.lib.common.entity.ApiResponse;
import com.spatio.serving.common.lib.common.enums.ApiResponseCode;
import com.spatio.serving.common.lib.common.exception.SpatioHttpInvokeException;
import com.spatio.serving.common.lib.common.exception.SpatioParameterInvalidException;
import com.spatio.serving.common.lib.common.exception.SpatioRuntimeException;
import com.spatio.serving.common.lib.common.util.SpatioRequestUtil;
import com.spatio.serving.spring.boot.starter.web.aspect.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.util.HashSet;
import java.util.Set;

/**
 * @author lei.zhao
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    private static Set<Integer> NORMAL_BUSINESS_CODE = null;

    static {
        NORMAL_BUSINESS_CODE = new HashSet<>();
        // system play uri=/unlock/inner/task_create or uri=/unlock/inner/task_join, normal business code
        NORMAL_BUSINESS_CODE.add(30100);
        NORMAL_BUSINESS_CODE.add(30101);
        NORMAL_BUSINESS_CODE.add(30102);
        NORMAL_BUSINESS_CODE.add(30103);
        NORMAL_BUSINESS_CODE.add(30104);
        NORMAL_BUSINESS_CODE.add(30105);
    }


    /**
     * trigger：SpatioRuntimeException
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(SpatioParameterInvalidException.class)
    @Api
    public Result handleSpatioParameterInvalidException(SpatioParameterInvalidException ex) {
        log.error("SpatioParameterInvalidException", ex);
        ApiResponseCode code = ex.getApiResponseCode();
        ApiResponse<Object> commonResult = ApiResponse.failure(
                code.getCode(),
                ex.getMessage(),
                code.getMsg(),
                SpatioRequestUtil.getRequestId(getRequest())
        );
        return Result.error(code.getCode(), ex.getMessage(), code.getMsg());
    }


    /**
     * trigger：SpatioRuntimeException
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(SpatioHttpInvokeException.class)
    @Api
    public Result handleSpatioHttpInvokeException(SpatioHttpInvokeException ex) {
        ApiResponse apiResponse = ex.getApiResponse();
        if (apiResponse == null || !NORMAL_BUSINESS_CODE.contains(apiResponse.getCode())) {
            log.error("SpatioHttpInvokeException", ex);
        }
        if (apiResponse != null) {
            log.warn("SpatioHttpInvokeException return business system response code is not 0, " +
                    "exception message={}, response={}", ex.getMessage(), apiResponse);
            return Result.error(500,null,apiResponse);
        }
        ApiResponseCode code = ex.getApiResponseCode();
        ApiResponse<Object> commonResult = ApiResponse.failure(
                code,
                SpatioRequestUtil.getRequestId(getRequest())
        );
        return Result.error(code.getCode(),null,SpatioRequestUtil.getRequestId(getRequest()));
    }

    /**
     * trigger：SpatioRuntimeException
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(SpatioRuntimeException.class)
    @Api
    public Result handleSpatioRuntimeException(SpatioRuntimeException ex) {
        log.error("SpatioRuntimeException", ex);
        ApiResponseCode code = ex.getApiResponseCode();
        ApiResponse<Object> commonResult = ApiResponse.failure(
                code,
                SpatioRequestUtil.getRequestId(getRequest())
        );
        return Result.error(code.getCode(),null,SpatioRequestUtil.getRequestId(getRequest()));
    }

    /**
     * trigger：@Valid field rule no match request body
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @Api
    public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        log.error("MethodArgumentNotValidException", ex);
        ApiResponse<Object> commonResult = ApiResponse.failure(
                ApiResponseCode.PARAM_INVALID.getCode(),
                ex.getBindingResult().getFieldError().getDefaultMessage(),
                ApiResponseCode.PARAM_INVALID.getMsg(),
                SpatioRequestUtil.getRequestId(getRequest())
        );
        return  Result.error(
                ApiResponseCode.PARAM_INVALID.getCode(),
                ex.getBindingResult().getFieldError().getDefaultMessage(),
                ApiResponseCode.PARAM_INVALID.getMsg()
        );
    }

    /**
     * trigger：parameter no match validation rule, eg: @NotNull annotation be added, and parameter lost
     *
     * @param e
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @Api
    public Result handleConstraintViolationException(ConstraintViolationException e) {
        log.error("ConstraintViolationException", e);
        ApiResponse<Object> commonResult = ApiResponse.failure(
                ApiResponseCode.PARAM_INVALID.getCode(),
                e.getMessage(),
                ApiResponseCode.PARAM_INVALID.getMsg(),
                SpatioRequestUtil.getRequestId(getRequest()));
        return Result.error(ApiResponseCode.PARAM_INVALID.getCode(),e.getMessage(),ApiResponseCode.PARAM_INVALID.getMsg());
    }


    /**
     * trigger: @RequestParam annotation be added, and parameter lost (key=value)
     *
     * @param e
     * @return
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @Api
    public Result handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        log.error("MissingServletRequestParameterException", e);
        ApiResponse<Object> commonResult = ApiResponse.failure(
                ApiResponseCode.PARAM_INVALID.getCode(),
                e.getMessage(),
                ApiResponseCode.PARAM_INVALID.getMsg(),
                SpatioRequestUtil.getRequestId(getRequest()));
        return Result.error(ApiResponseCode.PARAM_INVALID.getCode(),e.getMessage(),ApiResponseCode.PARAM_INVALID.getMsg());
    }

    /**
     * trigger：backup exception
     *
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    @Api
    public Result handleException(Exception e) {
        log.error("Exception", e);
        ApiResponse<Object> commonResult = ApiResponse.failure(
                ApiResponseCode.ERROR.getCode(),
                e.getMessage(),
                ApiResponseCode.ERROR.getMsg(),
                SpatioRequestUtil.getRequestId(getRequest()));
        return Result.error(ApiResponseCode.ERROR.getCode(),e.getMessage(),ApiResponseCode.ERROR.getMsg());
    }

    public HttpServletRequest getRequest() {
        return ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
    }
}
